package cn.isjinhao.crws.sql.grammar.retrieve.visitor;

import cn.isjinhao.crws.sql.grammar.Binary;
import cn.isjinhao.crws.sql.grammar.BinaryArithmetic;
import cn.isjinhao.crws.sql.grammar.Comparison;
import cn.isjinhao.crws.sql.grammar.CurdStatement;
import cn.isjinhao.crws.sql.grammar.Function;
import cn.isjinhao.crws.sql.grammar.GroupFunction;
import cn.isjinhao.crws.sql.grammar.Grouping;
import cn.isjinhao.crws.sql.grammar.Identifier;
import cn.isjinhao.crws.sql.grammar.Literal;
import cn.isjinhao.crws.sql.grammar.Logic;
import cn.isjinhao.crws.sql.grammar.UnaryArithmetic;
import cn.isjinhao.crws.sql.grammar.ParseException;
import cn.isjinhao.crws.sql.grammar.retrieve.SelectVisitor;
import cn.isjinhao.crws.sql.grammar.retrieve.SingleSelectMetaInfo;
import cn.isjinhao.crws.sql.grammar.retrieve.represention.Select;
import cn.isjinhao.crws.sql.grammar.retrieve.represention.SingleSelect;
import cn.isjinhao.crws.sql.grammar.retrieve.represention.column.ColumnRep;
import cn.isjinhao.crws.sql.grammar.retrieve.represention.column.ColumnSeg;
import cn.isjinhao.crws.sql.grammar.retrieve.represention.group.GroupBySeg;
import cn.isjinhao.crws.sql.grammar.retrieve.represention.limit.LimitSeg;
import cn.isjinhao.crws.sql.grammar.retrieve.represention.order.OrderBySeg;
import cn.isjinhao.crws.sql.grammar.retrieve.represention.table.TableRep;
import cn.isjinhao.crws.sql.grammar.retrieve.represention.table.TableSeg;
import cn.isjinhao.crws.sql.grammar.retrieve.represention.where.ExistsCondition;
import cn.isjinhao.crws.sql.grammar.retrieve.represention.where.InCondition;
import cn.isjinhao.crws.sql.grammar.retrieve.represention.where.RetrieveWhereSeg;
import cn.isjinhao.crws.sql.lexical.token.Token;
import cn.isjinhao.crws.sql.lexical.token.TokenType;

import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * 最终的目的是找出来可运行的 SelectStatement 及其对应的环境。本质是计算singleSelect对集合和字段的引用情况。
 *
 * @Author ISJINHAO
 * @Date 2021/3/10 19:24
 */
public class SingleSelectMetaInfoDetector extends SelectVisitor<Set<Token>> {

    private int depth = -1;

    private int order = -1;

    private SingleSelectMetaInfo singleSelectMetaInfo = null;

    @Override
    public synchronized Set<Token> visitSelect(Select select) {
        init();
        Set<Token> tokens = visitBinary(select);
        clear();
        return tokens;
    }

    private void init() {
        singleSelectMetaInfo = new SingleSelectMetaInfo(depth, order);
    }

    private void clear() {
        depth = -1;
        order = -1;
    }

    @Override
    public Set<Token> visitSingleSelect(SingleSelect singleSelect) {
        pushStack(singleSelect);

        Set<Token> resultColumns = singleSelect.getColumnSeg().accept(this);
        singleSelectMetaInfo.addResultColumnSet(resultColumns);

        Set<Token> referenceTables = singleSelect.getTableSeg().accept(this);
        singleSelectMetaInfo.addReferenceTableSet(referenceTables);

        CurdStatement whereSeg = singleSelect.getWhereSeg();
        if(whereSeg != null) {
            // bug
            singleSelectMetaInfo.addReferenceColumnSet(whereSeg.accept(this));
        }
        CurdStatement groupBySeg = singleSelect.getGroupBySeg();
        if(groupBySeg != null) {
            singleSelectMetaInfo.addReferenceColumnSet(groupBySeg.accept(this));
        }
        CurdStatement orderBySeg = singleSelect.getOrderBySeg();
        if(orderBySeg != null) {
            singleSelectMetaInfo.addReferenceColumnSet(orderBySeg.accept(this));
        }

        popStack();
        expandSingleSelectAstMetaInfo(singleSelect);
        return new HashSet<>();
    }

    private static final Token STAR_TOKEN = new Token(TokenType.STAR, "*");
    private void expandSingleSelectAstMetaInfo(SingleSelect singleSelect) {
        SingleSelectMetaInfo astMetaInfo = singleSelect.getSingleSelectAstMetaInfo();
        Set<Token> referenceColumnSet = astMetaInfo.getReferenceColumnSet();
        Set<Token> returnColumnSet = astMetaInfo.getReturnColumnSet();
        Set<Token> referenceTableSet = astMetaInfo.getReferenceTableSet();

        Token tableName = null;
        boolean accurate = false;
        if(referenceTableSet.size() == 1) {
            tableName = (Token) referenceTableSet.toArray()[0];
            accurate = true;
        } else if(referenceTableSet.isEmpty()) {
            throw new ParseException("select 语句至少操作一个表");
        }
        if(accurate && referenceColumnSet.remove(STAR_TOKEN)) {
            Token newStar = new Token(TokenType.IDENTIFIER, tableName.getLiteral() + ".*");
            referenceColumnSet.add(newStar);
        }
        if(accurate && returnColumnSet.remove(STAR_TOKEN)) {
            Token newStar = new Token(TokenType.IDENTIFIER, tableName.getLiteral() + ".*");
            returnColumnSet.add(newStar);
        }
    }

    private void pushStack(SingleSelect singleSelect) {
        depth++;
        order++;
        SingleSelectMetaInfo parent = this.singleSelectMetaInfo;
        singleSelectMetaInfo = new SingleSelectMetaInfo(singleSelect, parent, depth, order);
        parent.addChild(singleSelectMetaInfo);
        singleSelect.setAstMetaInfo(singleSelectMetaInfo);
    }

    /**
     * @return 返回的是添加returnColumn，side-effect是添加innerColumn。
     */
    @Override
    public Set<Token> visitColumnRep(ColumnRep columnRep) {
        Set<Token> returnColumnSet = new HashSet<>();

        CurdStatement curdStatement = columnRep.getSelectStatement();
        if(curdStatement == null) {
            throw new ParseException("select语句的返回列由逗号分割的标识符数组组成");
        }
        Token operator = columnRep.getOperator();
        Set<Token> accept = curdStatement.accept(this);
        if(accept == null || accept.size() > 1) {
            throw new ParseException("select语句的返回列由逗号分割的标识符数组组成");
        }
        if(curdStatement instanceof Function) {
            return returnColumnSet;
        }
        // 最多能接受一列
        Token column = (Token) accept.toArray()[0];
        singleSelectMetaInfo.addReferenceColumn(column);
        returnColumnSet.add(column);
        if(operator != null) {
            // 设置别名
            singleSelectMetaInfo.putColumnAlias(curdStatement, operator);
        }
        return returnColumnSet;
    }

    private void popStack() {
        singleSelectMetaInfo = singleSelectMetaInfo.getParent();
        depth--;
    }

    /**
     * @return 返回的是returnColumn。
     */
    @Override
    public Set<Token> visitColumnSeg(ColumnSeg columnSeg) {
        Set<Token> columns = new HashSet<>();
        List<CurdStatement> columnRepList = columnSeg.getColumnRepList();
        for(CurdStatement curdStatement : columnRepList) {
            Set<Token> accept = curdStatement.accept(this);
            // ColumnRep 返回的Set只会存在一个值。
            if(accept.size() == 1) {
                Token token = (Token) accept.toArray()[0];
                if(TokenType.STAR.equals(token.getType()) && columns.contains(token)) {
                    throw new ParseException("select 语句的返回值只能存在一个 '*'");
                }
                columns.add(token);
            }
        }
        return columns;
    }

    /**
     * @return 返回的是引用的表。
     */
    @Override
    public Set<Token> visitTableSeg(TableSeg tableSeg) {
        // 表的连接条件是innerColumn
        CurdStatement condition = tableSeg.getCondition();
        if(condition != null) {
            Set<Token> accept = condition.accept(this);
            singleSelectMetaInfo.addReferenceColumnSet(accept);
        }
        return visitBinary(tableSeg);
    }

    /**
     * 三种情况：
     *  1、Identifier Identifier：前者是真实表，后者是表名。
     *  2、Identifier：真实表。
     *  3、SingleSelectValue Identifier：前者是真实表，后者是表名。
     * 在第一种情况下，前者是引用的表，后者是select语句使用的表。这种情况下，和第三种情况一样，前者被认为是临时表。
     *
     * @return 返回的是引用的表
     */
    @Override
    public Set<Token> visitTableRep(TableRep tableRep) {
        Set<Token> tokenList = new HashSet<>();
        CurdStatement curdStatement = tableRep.getSelectStatement();
        Token alias = tableRep.getOperator();

        // Identifier Identifier
        if((curdStatement instanceof Identifier) && alias != null) {
            Token tableName = ((Identifier) curdStatement).getName();
            singleSelectMetaInfo.putTempTable(curdStatement, alias);
            tokenList.add(tableName);
        }
        // SingleSelectValue Identifier
        else if ((curdStatement instanceof SingleSelect) && alias != null) {
            singleSelectMetaInfo.putTempTable(curdStatement, alias);
            tokenList.add(alias);
            curdStatement.accept(this);
        }
        // Identifier
        else if(curdStatement instanceof Identifier) {
            Token tableName = ((Identifier) curdStatement).getName();
            tokenList.add(tableName);
        } else {
            throw new ParseException("select语句作为真实表的时候必须存在别名");
        }
        return tokenList;
    }

    /**
     * 返回的是 innerColumn
     * @param retrieveWhereSeg
     */
    @Override
    public Set<Token> visitRetrieveWhereSeg(RetrieveWhereSeg retrieveWhereSeg) {
        return retrieveWhereSeg.getSelectStatement().accept(this);
    }

    @Override
    public Set<Token> visitInCondition(InCondition inCondition) {
        Set<Token> tokenList = new HashSet<>();

        // 列名要存起来
        Token identifier = inCondition.getIdentifier();
        tokenList.add(identifier);

        // singleSelect要递归
        CurdStatement curdStatement = inCondition.getSelectStatement();
        if(curdStatement instanceof Select) {
            curdStatement.accept(this);
        }
        return tokenList;
    }

    @Override
    public Set<Token> visitExistsCondition(ExistsCondition existsCondition) {
        // ExistsCondition 的 operator 存的是 exists 或 not exists

        CurdStatement curdStatement = existsCondition.getSelectStatement();
        curdStatement.accept(this);

        return new HashSet<>();
    }

    /**
     * 返回的是 innerColumns
     * @param groupBySeg
     */
    @Override
    public Set<Token> visitGroupBySeg(GroupBySeg groupBySeg) {
        Set<Token> set = new HashSet<>();
        List<Token> columnList = groupBySeg.getColumnList();
        set.addAll(columnList);
        CurdStatement having = groupBySeg.getHaving();
        if(having != null) {
            set.addAll(having.accept(this));
        }
        return set;
    }

    /**
     * 返回的是 innerColumns
     * @param orderBySeg
     */
    @Override
    public Set<Token> visitOrderBySeg(OrderBySeg orderBySeg) {
        List<OrderBySeg.OrderItem> columnList = orderBySeg.getColumnList();
        return columnList.stream().map(OrderBySeg.OrderItem::getColumn).collect(Collectors.toSet());
    }

    @Override
    public Set<Token> visitLimitSeg(LimitSeg limitSeg) {
        return null;
    }

    @Override
    public Set<Token> visitLogic(Logic logic) {
        return visitBinary(logic);
    }

    @Override
    public Set<Token> visitComparison(Comparison comparison) {
        return visitBinary(comparison);
    }

    @Override
    public Set<Token> visitBinaryArithmetic(BinaryArithmetic binaryArithmetic) {
        return visitBinary(binaryArithmetic);
    }

    private Set<Token> visitBinary(Binary binary) {
        CurdStatement left = binary.getLeftSelectStatement();
        Set<Token> accept = left.accept(this);
        CurdStatement right = binary.getRightSelectStatement();
        if(right != null) {
            Set<Token> rightValue = right.accept(this);
            if(rightValue != null && !rightValue.isEmpty()) {
                accept.addAll(rightValue);
            }
        }
        return accept;
    }

    @Override
    public Set<Token> visitUnaryArithmetic(UnaryArithmetic unaryArithmetic) {
        CurdStatement curdStatement = unaryArithmetic.getSelectStatement();
        return curdStatement.accept(this);
    }

    @Override
    public Set<Token> visitLiteral(Literal literal) {
        return null;
    }

    @Override
    public Set<Token> visitGroupFunction(GroupFunction groupFunction) {
        Set<Token> set = new HashSet<>();
        Token token = groupFunction.getToken();
        if(!token.getType().equals(TokenType.STAR)) {
            set.add(token);
        } else {
            set.add(new Token(TokenType.PLACEHOLDER, "groupFunction_star"));
        }
        return set;
    }

    @Override
    public Set<Token> visitGrouping(Grouping grouping) {
        return grouping.getSelectStatement().accept(this);
    }

    @Override
    public Set<Token> visitIdentifier(Identifier identifier) {
        Set<Token> set = new HashSet<>();
        set.add(identifier.getName());
        return set;
    }

    @Override
    public Set<Token> visitFunction(Function function) {
        Set<Token> set = new HashSet<>();
        List<CurdStatement> parameterList = function.getParameterList();
        if(parameterList == null) {
            return set;
        }
        for(CurdStatement curdStatement : parameterList) {
            // 这里的条件是不包含 Literal 的，因为 literal 是文本，不是需要引用的列
            if(curdStatement instanceof Function || curdStatement instanceof Identifier || curdStatement instanceof BinaryArithmetic
                    || curdStatement instanceof UnaryArithmetic ) {
                set.addAll(curdStatement.accept(this));
            }
            // Literal 直接跳过，不抛异常也不计算
            else if (!(curdStatement instanceof Literal)) {
                throw new ParseException("函数的参数列表，只能由函数、标识符、表达式和文本组成");
            }
        }
        return set;
    }

}
